zhouqijie

序、OpenGL曲面细分

关于Tessallation

Tessallation(镶嵌)在图形学通常是指曲面细分。

Patch:

Patch是曲面细分着色器使用的一种新的图元类型 (GL_PATCHES)。Patch仅仅是传入到OpenGL的一组顶点(控制点)。

使用一个patch时,需要告诉OpenGL顶点数组中要使用多少个顶点来组成一个patch,而这可以通过使用glPatchParameteri()进行指定。

OpenGL对曲面细分的支持通过三个管线阶段提供:

  1. 曲面细分控制着色器。(可编程)(指定曲面细分器要生成的网格拓扑结构)
  2. 曲面细分器。
  3. 曲面细分评估着色器。 (可编程)(允许以各种方式操纵网格)


曲面细分过程中数据在管线的传递

顶点着色器:

=>

position

texcoord

...

gl_Position

texcoord

...

=>

曲面细分控制着色器(TCS):

=>

gl_in[gl_InvocationID].gl_Position

texcoord[gl_InvocationID]

...

gl_out[gl_InvocationID].gl_Position

texcoord_tcs_out[gl_InvocationID]

...

=>


曲面细分计算着色器(TES):

=>

gl_in[gl_InvocationID].gl_Position

texcoord_tcs_out[gl_InvocationID]

...

gl_Position

texcoord_tes_out

...

=>


实现示例一、16控制点贝塞尔曲面

App:

float vertices[16 * 3] =
{
	-1.0f, 0.5f, -1.0f, -0.5f, 0.5f, -1.0f, 0.5f, 0.5f, -1.0f, 1.0f, 0.5f, -1.0f,
	-1.0f, 0.0f, -0.5f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f, -0.5f, 1.0f, 0.0f, -0.5f,
	-1.0f, 0.0f,  0.5f, -0.5f, 0.0f,  0.5f, 0.5f, 0.0f,  0.5f, 1.0f, 0.0f,  0.5f,
	-1.0f,-0.5f,  1.0f, -0.5f, 0.3f,  1.0f, 0.5f, 0.3f,  1.0f, 1.0f, 0.3f,  1.0f
};
glGenVertexArrays(numVAOs, vao);
glBindVertexArray(vao[0]);
glGenBuffers(numVBOs, vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glBufferData(GL_ARRAY_BUFFER, 16 * 3 * 4, &vertices[0], GL_STATIC_DRAW);
//...
glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);

glPatchParameteri(GL_PATCH_VERTICES, 16);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArrays(GL_PATCHES, 0, 16);

顶点着色器:

#version 430
out vec2 texcoord;
layout (location = 0) in vec3 position;

void main(void)
{	
	texcoord = vec2((position.x + 1.0)/2.0, (position.z + 1.0)/2.0);//为当前顶点计算纹理坐标
	gl_Position = vec4(position, 1.0); //原始顶点。TES阶段再MVP变换
}

TCS:

#version 430

in vec2 texcoord[];
out vec2 texcoord_tcs_out[];

layout (vertices = 16) out;

void main(void)
{	int TL = 6;  // 曲面细分等级(内外都是32)
	if (gl_InvocationID ==0)
	{	gl_TessLevelOuter[0] = TL;
		gl_TessLevelOuter[2] = TL;
		gl_TessLevelOuter[1] = TL;
		gl_TessLevelOuter[3] = TL;
		gl_TessLevelInner[0] = TL;
		gl_TessLevelInner[1] = TL;
	}
	texcoord_tcs_out[gl_InvocationID] = texcoord[gl_InvocationID];//纹理坐标传递
	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;//传递gl_Position属性
}
//gl_InvocationID--整型id计数器,指示TCS正在执行哪个调用,常用于传递顶点属性比如gl_Position。
//gl_in[]--传入的控制点数组。每个控制点是一个数组元素,可以访问其顶点属性。
//gl_out[]--输出给TES的控制点数组。用法同上。

TES:

#version 430

layout (quads, equal_spacing,ccw) in;//细分选项:四边形,等距细分、顶点逆时针缠绕。
//补充:如果我们想输出单个点,而不是等值线或填充区域的话,我们可以应用point_mode选项。

uniform mat4 mvp_matrix;

in vec2 texcoord_tcs_out[];
out vec2 texcoord_tes_out;

void main (void)
{	vec3 p00 = (gl_in[0].gl_Position).xyz;
	vec3 p10 = (gl_in[1].gl_Position).xyz;
	vec3 p20 = (gl_in[2].gl_Position).xyz;
	vec3 p30 = (gl_in[3].gl_Position).xyz;
	vec3 p01 = (gl_in[4].gl_Position).xyz;
	vec3 p11 = (gl_in[5].gl_Position).xyz;
	vec3 p21 = (gl_in[6].gl_Position).xyz;
	vec3 p31 = (gl_in[7].gl_Position).xyz;
	vec3 p02 = (gl_in[8].gl_Position).xyz;
	vec3 p12 = (gl_in[9].gl_Position).xyz;
	vec3 p22 = (gl_in[10].gl_Position).xyz;
	vec3 p32 = (gl_in[11].gl_Position).xyz;
	vec3 p03 = (gl_in[12].gl_Position).xyz;
	vec3 p13 = (gl_in[13].gl_Position).xyz;
	vec3 p23 = (gl_in[14].gl_Position).xyz;
	vec3 p33 = (gl_in[15].gl_Position).xyz;

	// gl_TessCoord保存的是细分网格点位置。这个位置具有两个或三个分量,依赖于patch的类型。
	//(只有在TES中可用)(范围:[0, 1])
	float u = gl_TessCoord.x;
	float v = gl_TessCoord.y;
	
	// 三次贝塞尔基础函数
	float bu0 = (1.0-u)  * (1.0-u)  * (1.0-u);	//(1-u)^3
	float bu1 = 3.0 * u * (1.0-u) * (1.0-u);	//3u(1-u)^2  
	float bu2 = 3. * u * u * (1.0-u);			//3u^2(1-u)
	float bu3 = u * u * u;						//u^3
	float bv0 = (1.0-v)  * (1.0-v)  * (1.0-v);	//(1-v)^3
	float bv1 = 3.0 * v * (1.0-v) * (1.0-v);	//3v(1-v)^2  
	float bv2 = 3. * v * v * (1.0-v);			//3v^2(1-v)
	float bv3 = v * v * v;						//v^3
	
	// 输出细分后的顶点位置
	vec3 outputPosition =
		  bu0 * ( bv0*p00 + bv1*p01 + bv2*p02 + bv3*p03 )
		+ bu1 * ( bv0*p10 + bv1*p11 + bv2*p12 + bv3*p13 )
		+ bu2 * ( bv0*p20 + bv1*p21 + bv2*p22 + bv3*p23 )
		+ bu3 * ( bv0*p30 + bv1*p31 + bv2*p32 + bv3*p33 );
	//最终位置
	gl_Position = mvp_matrix * vec4(outputPosition,1.0f);
	
	// 输出插值后的纹理坐标
	vec2 tc1 = mix(texcoord_tcs_out[0], texcoord_tcs_out[3], gl_TessCoord.x);
	vec2 tc2 = mix(texcoord_tcs_out[12], texcoord_tcs_out[15], gl_TessCoord.x);
	vec2 tc = mix(tc2, tc1, gl_TessCoord.y);
	texcoord_tes_out = tc;
}

片段着色器:

#version 430

in vec2 texcoord_tes_out;
out vec4 color;
uniform mat4 mvp_matrix;

layout (binding=0) uniform sampler2D tex_color;

void main(void)
{
	color = texture(tex_color, texcoord_tes_out);
}

实现示例二、地形细分

说明:

  1. 使用了LOD技术,显著降低了系统负载。
  2. 使用了Instance机制,因为曲面细分值最大64x64,不足以满足地形精细度需求。

补充:

  1. 可以采样法线贴图来实现地形的光照效果。
  2. 可以通过把摄像机后面的Patch外部和内部层级设置为0来实现剔除技术。

代码:

App:

//......
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, glm::value_ptr(mvpMat));
//......
glPatchParameteri(GL_PATCH_VERTICES, 4);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glDrawArraysInstanced(GL_PATCHES, 0, 4, 64*64);
//......

Vert Shader:

#version 430

out vec2 texcoord;

uniform mat4 mvp_matrix;
uniform float length_side;//地形边长

layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;

void main(void)
{	
	
	// 基于InstanceID指定偏移
	int x = gl_InstanceID % 64;
	int y = gl_InstanceID / 64;

	
	// 纹理坐标分配进64个Patch实例。(归一化为[0, 1],翻转Y坐标)
	const vec2 patchTexCoords[] = vec2[](vec2(0,0), vec2(1,0), vec2(0,1), vec2(1,1));
		
	texcoord = vec2( (x + patchTexCoords[gl_VertexID].x) / 64.0,
			(63 - y + patchTexCoords[gl_VertexID].y) / 64.0);
	
	// 顶点位置 (从-0.5边长到+0.5边长)
	gl_Position = vec4((texcoord.x - 0.5) * length_side, 0.0, ((1.0 - texcoord.y) - 0.5) * length_side, 1.0);
}

TCS:

#version 430

layout (vertices = 4) out;

in vec2 texcoord[];
out vec2 texcoord_tcs_out[];

uniform mat4 mvp_matrix;
layout (binding=0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;

void main(void)
{	//设置LOD级别
	float subdivisions = 16.0;//可调整常量
	if (gl_InvocationID == 0)
	{ 	vec4 p0 = mvp_matrix * gl_in[0].gl_Position;  p0 = p0 / p0.w;
		vec4 p1 = mvp_matrix * gl_in[1].gl_Position;  p1 = p1 / p1.w;
		vec4 p2 = mvp_matrix * gl_in[2].gl_Position;  p2 = p2 / p2.w;
		float width  = length(p1-p0) * subdivisions + 1.0;
		float height = length(p2-p0) * subdivisions + 1.0;
		gl_TessLevelOuter[0] = height;
		gl_TessLevelOuter[1] = width;
		gl_TessLevelOuter[2] = height;
		gl_TessLevelOuter[3] = width;
		gl_TessLevelInner[0] = width;
		gl_TessLevelInner[1] = height;
	}
	
	//传递位置和纹理坐标
	texcoord_tcs_out[gl_InvocationID] = texcoord[gl_InvocationID];
	gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
}

TES:

#version 430

layout (quads, fractional_even_spacing, ccw) in;//等间距改为分数间距。(避免LOD突变)
//layout (quads, equal_spacing, ccw) in; 

uniform mat4 mvp_matrix;
uniform float length_side;
layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;

in vec2 texcoord_tcs_out[];
out vec2 texcoord_tes_out;

void main (void)
{	//子网格顶点纹理坐标
	vec2 texcoord = vec2(texcoord_tcs_out[0].x + (gl_TessCoord.x) / 64.0,
					texcoord_tcs_out[0].y + (1.0 - gl_TessCoord.y) / 64.0);

	// 子网格顶点位置
	vec4 tessellatedPoint = vec4(gl_in[0].gl_Position.x + gl_TessCoord.x * length_side / 64.0, 0.0,
								gl_in[0].gl_Position.z + gl_TessCoord.y * length_side / 64.0, 1.0);
	
	// 采样高度的高度值增加给顶点
	tessellatedPoint.y += (texture(tex_height, texcoord).r) / 60.0;
	
	//输出顶点位置和纹理坐标准备插值
	gl_Position = mvp_matrix * tessellatedPoint;
	texcoord_tes_out = texcoord;	
}

Frag Shader:

#version 430

in vec2 texcoord_tes_out;
out vec4 color;
uniform mat4 mvp_matrix;

layout (binding = 0) uniform sampler2D tex_color;
layout (binding = 1) uniform sampler2D tex_height;

void main(void)
{	color = texture(tex_color, texcoord_tes_out);
}